package com.ys168.gam.shortestpath.view;

import static com.ys168.gam.shortestpath.message.Messages.getString;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileNameExtensionFilter;

import com.ys168.gam.shortestpath.algorithm.Dijkstra;
import com.ys168.gam.shortestpath.message.Messages;
import com.ys168.gam.shortestpath.message.MessagesCode;
import com.ys168.gam.shortestpath.model.Node;
import com.ys168.gam.shortestpath.persistent.DataExporter;
import com.ys168.gam.shortestpath.persistent.DataImporter;

/**
 *
 * @author Jason Krebs
 * @date 2015年2月3日
 */
public class MainFrame extends JFrame {

    private static final Logger LOGGER = Logger.getLogger(MainFrame.class.getName());
    private static final long serialVersionUID = -4579959636868548754L;

    private CanvasPanel canvasPanel;
    private JButton cleanAllButton;

    private JButton cleanLinesButton;
    private JCheckBox directedGraphCheckbox;

    private JRadioButton drawNodeRadio;
    private JButton exchangeButton;

    private JRadioButton moveNodeRadio;
    private JPopupMenu popupMenu;

    private JButton runOrStopButton;
    private JCheckBox showWeightCheckbox;
    private JLabel tipLabel;

    public MainFrame() {
        setTitle(getString(MessagesCode.APPLICATION_NAME));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setIconImage(CanvasPanel.NODE_IMAGE);
        setSize(700, 550);

        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        setLocation((screen.width - getWidth()) / 2, (screen.height - getHeight()) / 2);

        createMenu();
        createContent();
        createPopupMenu();

        addListener();
        init();
    }

    private void addListener() {
        new DropTarget(canvasPanel, new DropTargetAdapter() {
            @Override
            public void drop(DropTargetDropEvent dropEvent) {
                onDrop(dropEvent);
            }
        });
        canvasPanel.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                onMouseDragged(e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                onMouseMoved(e.getPoint());
            }
        });
        canvasPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                onMouseClick(e);
            }
        });
        canvasPanel.setOnRunningFinshed(new CanvasPanel.IRunningFinishedListener() {
            @Override
            public void onRunningFinished() {
                setRunStatus(false);
            }
        });
        showWeightCheckbox.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                canvasPanel.setShowWeight(showWeightCheckbox.isSelected());
                canvasPanel.repaint();
            }
        });
        directedGraphCheckbox.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                canvasPanel.setDirectedGraph(directedGraphCheckbox.isSelected());
                canvasPanel.repaint();
            }
        });

        runOrStopButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onRunOrStop();
            }
        });
        exchangeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvasPanel.exchange();
            }
        });
        cleanLinesButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvasPanel.cleanLines();
            }
        });
        cleanAllButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvasPanel.cleanAll();
            }
        });
    }

    private void createContent() {
        JPanel contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout(0, 0));

        canvasPanel = new CanvasPanel();
        canvasPanel.setPreferredSize(new Dimension(600, 500));

        canvasPanel.setForeground(Color.WHITE);
        canvasPanel.setBackground(Color.BLACK);
        canvasPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));

        JScrollPane scrollPane = new JScrollPane(canvasPanel);
        contentPane.add(scrollPane, BorderLayout.CENTER);

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        contentPane.add(toolBar, BorderLayout.SOUTH);

        tipLabel = new JLabel("x:y:");
        toolBar.add(tipLabel);

        JPanel controlPanel = new JPanel();
        contentPane.add(controlPanel, BorderLayout.EAST);

        GridBagLayout gbl_controlPanel = new GridBagLayout();
        gbl_controlPanel.columnWidths = new int[] { 175, 0 };
        gbl_controlPanel.rowHeights = new int[] { 100, 100, 300 };
        gbl_controlPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
        gbl_controlPanel.rowWeights = new double[] { 0.0, 0.0, 1.0 };
        controlPanel.setLayout(gbl_controlPanel);

        JPanel drawPanel = new JPanel();
        drawPanel.setBorder(new TitledBorder(null, getString(MessagesCode.MODE), TitledBorder.LEADING, TitledBorder.TOP, null, null)); // $NON-NLS-1$
        GridBagConstraints gbc_drawPanel = new GridBagConstraints();
        gbc_drawPanel.fill = GridBagConstraints.BOTH;
        gbc_drawPanel.insets = new Insets(0, 0, 5, 0);
        gbc_drawPanel.gridx = 0;
        gbc_drawPanel.gridy = 0;
        controlPanel.add(drawPanel, gbc_drawPanel);
        drawPanel.setLayout(new BoxLayout(drawPanel, BoxLayout.Y_AXIS));

        drawNodeRadio = new JRadioButton(getString(MessagesCode.DRAW_NODE));
        drawPanel.add(drawNodeRadio);

        moveNodeRadio = new JRadioButton(getString(MessagesCode.MOVE_NODE));
        drawPanel.add(moveNodeRadio);

        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(drawNodeRadio);
        buttonGroup.add(moveNodeRadio);

        JPanel extendPanel = new JPanel();
        extendPanel.setBorder(new TitledBorder(null, getString(MessagesCode.EXTEND), TitledBorder.LEADING, TitledBorder.TOP, null, null));
        GridBagConstraints gbc_extendPanel = new GridBagConstraints();
        gbc_extendPanel.fill = GridBagConstraints.HORIZONTAL;
        gbc_extendPanel.insets = new Insets(0, 0, 5, 0);
        gbc_extendPanel.gridx = 0;
        gbc_extendPanel.gridy = 1;
        controlPanel.add(extendPanel, gbc_extendPanel);
        extendPanel.setLayout(new BoxLayout(extendPanel, BoxLayout.Y_AXIS));

        showWeightCheckbox = new JCheckBox(getString(MessagesCode.SHOW_WEIGHT));
        extendPanel.add(showWeightCheckbox);

        directedGraphCheckbox = new JCheckBox(getString(MessagesCode.DIRECTED_GRAPH));
        extendPanel.add(directedGraphCheckbox);

        JPanel operatePanel = new JPanel();
        operatePanel.setBorder(new TitledBorder(null, Messages.getString("Operate"), TitledBorder.LEADING, TitledBorder.TOP, null, null)); //$NON-NLS-1$
        GridBagConstraints gbc_operatePanel = new GridBagConstraints();
        gbc_operatePanel.anchor = GridBagConstraints.NORTH;
        gbc_operatePanel.fill = GridBagConstraints.HORIZONTAL;
        gbc_operatePanel.gridx = 0;
        gbc_operatePanel.gridy = 2;
        controlPanel.add(operatePanel, gbc_operatePanel);
        GridBagLayout gbl_operatePanel = new GridBagLayout();
        gbl_operatePanel.columnWeights = new double[] { 0.0 };
        operatePanel.setLayout(gbl_operatePanel);

        runOrStopButton = new JButton(getString(MessagesCode.RUN));
        GridBagConstraints gbc_runOrStopButton = new GridBagConstraints();
        gbc_runOrStopButton.fill = GridBagConstraints.BOTH;
        gbc_runOrStopButton.insets = new Insets(0, 0, 5, 0);
        gbc_runOrStopButton.gridx = 0;
        gbc_runOrStopButton.gridy = 0;
        operatePanel.add(runOrStopButton, gbc_runOrStopButton);
        runOrStopButton.setForeground(Color.BLUE);

        exchangeButton = new JButton(getString(MessagesCode.EXCHANGE)); // $NON-NLS-1$
        GridBagConstraints gbc_exchangeButton = new GridBagConstraints();
        gbc_exchangeButton.fill = GridBagConstraints.HORIZONTAL;
        gbc_exchangeButton.insets = new Insets(0, 0, 5, 0);
        gbc_exchangeButton.gridx = 0;
        gbc_exchangeButton.gridy = 1;
        operatePanel.add(exchangeButton, gbc_exchangeButton);

        cleanLinesButton = new JButton(getString("CleanLine")); //$NON-NLS-1$
        GridBagConstraints gbc_cleanLinesButton = new GridBagConstraints();
        gbc_cleanLinesButton.fill = GridBagConstraints.HORIZONTAL;
        gbc_cleanLinesButton.insets = new Insets(0, 0, 5, 0);
        gbc_cleanLinesButton.gridx = 0;
        gbc_cleanLinesButton.gridy = 2;
        operatePanel.add(cleanLinesButton, gbc_cleanLinesButton);

        cleanAllButton = new JButton(getString(MessagesCode.CLEAN_ALL));
        GridBagConstraints gbc_clearAllButton = new GridBagConstraints();
        gbc_clearAllButton.fill = GridBagConstraints.BOTH;
        gbc_clearAllButton.gridx = 0;
        gbc_clearAllButton.gridy = 3;
        operatePanel.add(cleanAllButton, gbc_clearAllButton);
        cleanAllButton.setForeground(Color.RED);
    }

    private JMenu createMenu() {
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu fileMenu = new JMenu(getString(MessagesCode.FILE));
        menuBar.add(fileMenu);

        JMenuItem importMenuItem = new JMenuItem(getString(MessagesCode.IMPORT));
        importMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_MASK));
        fileMenu.add(importMenuItem);

        JMenuItem exportMenuItem = new JMenuItem(getString(MessagesCode.EXPORT));
        exportMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
        fileMenu.add(exportMenuItem);

        JMenuItem exitMenuItem = new JMenuItem(getString(MessagesCode.EXIT));
        exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.ALT_MASK));
        fileMenu.add(exitMenuItem);

        JMenuItem helpMenuItem = new JMenu(getString(MessagesCode.HELP));
        menuBar.add(helpMenuItem);

        JMenuItem infomationMenuItem = new JMenuItem(getString(MessagesCode.INFORMATION));
        helpMenuItem.add(infomationMenuItem);

        JMenuItem aboutMenuItem = new JMenuItem(getString(MessagesCode.ABOUT));
        helpMenuItem.add(aboutMenuItem);

        exitMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MainFrame.this.dispose();
            }
        });
        infomationMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onShowInformation();
            }
        });
        aboutMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(MainFrame.this, getString(MessagesCode.INFO_02));
            }
        });
        exportMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onExport();
            }
        });
        importMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                onImport();
            }
        });

        return fileMenu;
    }

    private void createPopupMenu() {
        popupMenu = new JPopupMenu();

        JMenuItem evictNodeMenuItem = new JMenuItem(getString(MessagesCode.REMOVE_NODE));
        popupMenu.add(evictNodeMenuItem);

        JMenuItem evictLineMenuItem = new JMenuItem(getString(MessagesCode.REMOVE_LINE));
        popupMenu.add(evictLineMenuItem);

        popupMenu.addSeparator();

        JMenuItem setStartMenuItem = new JMenuItem(getString(MessagesCode.SET_START));
        popupMenu.add(setStartMenuItem);

        JMenuItem setEndMenuItem = new JMenuItem(getString(MessagesCode.SET_END));
        popupMenu.add(setEndMenuItem);

        evictNodeMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvasPanel.removeNode();
            }
        });
        evictLineMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (!canvasPanel.removeLine()) {
                    tipLabel.setText(getString(MessagesCode.MESSAGE_07));
                }
            }
        });

        setStartMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvasPanel.setStart();
            }
        });

        setEndMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvasPanel.setEnd();
            }
        });

        canvasPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    Node node = canvasPanel.getNode(e.getPoint());
                    if (node == null) {
                        return;
                    }
                    if (e.isPopupTrigger()) {
                        popupMenu.show(e.getComponent(), e.getX(), e.getY());
                    }
                }
            }
        });
    }

    private void init() {
        drawNodeRadio.setSelected(true);
        directedGraphCheckbox.setSelected(true);
        canvasPanel.setDirectedGraph(true);
    }

    private void onDrop(DropTargetDropEvent dropEvent) {
        try {
            if (!dropEvent.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                dropEvent.rejectDrop();
                return;
            }

            dropEvent.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
            List<?> list = (List<?>) (dropEvent.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
            Iterator<?> iterator = list.iterator();
            while (iterator.hasNext()) {
                File f = (File) iterator.next();
                String path = f.getAbsolutePath().toLowerCase();
                if (path.endsWith(".json")) {
                    List<Node> nodes = DataImporter.importFrom(path, CanvasPanel.NODE_IMAGE);
                    canvasPanel.loadNodes(nodes);
                    tipLabel.setText(String.format(getString(MessagesCode.MESSAGE_01), nodes.size()));
                }
            }
            dropEvent.dropComplete(true);
        }
        catch (Exception e) {
            LOGGER.log(Level.WARNING, e.getMessage());
        }
    }

    private void onExport() {
        if (canvasPanel.getNodeCount() == 0) {
            tipLabel.setText(getString(MessagesCode.MESSAGE_02));
            return;
        }
        tipLabel.setText(DataExporter.doExport(canvasPanel.getAllNodes()));
    }

    private void onImport() {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle(getString(MessagesCode.MESSAGE_05));
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        chooser.setFileFilter(new FileNameExtensionFilter("*.json", "json"));

        if (chooser.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
            File selected = chooser.getSelectedFile();
            if (selected != null) {
                String path = selected.getPath();
                List<Node> nodes = DataImporter.importFrom(path, CanvasPanel.NODE_IMAGE);
                canvasPanel.loadNodes(nodes);
                tipLabel.setText(String.format(getString(MessagesCode.MESSAGE_01), nodes.size()));
            }
        }
    }

    private void onMouseClick(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON1) {
            if (drawNodeRadio.isSelected()) {
                canvasPanel.generateNode(e.getPoint());
            }
        }
        else if (e.getButton() == MouseEvent.BUTTON3) {
            if (canvasPanel.getFocusedNode() == null) {
                return;
            }
            if (e.isPopupTrigger()) {
                popupMenu.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    }

    private void onMouseDragged(MouseEvent e) {
        if (moveNodeRadio.isSelected()) {
            canvasPanel.moveNode(e.getPoint());
        }
    }

    private void onMouseMoved(Point point) {
        Node focused = canvasPanel.getFocusedNode();
        double fx = focused == null ? 0 : focused.getLocation().getX();
        double fy = focused == null ? 0 : focused.getLocation().getY();
        tipLabel.setText(String.format("X=%d,Y=%d,N=%d,L=%d,FX=%.2f,FY=%.2f", point.x, point.y, canvasPanel.getNodeCount(), canvasPanel.getLineCount(), fx, fy));
    }

    private void onRunOrStop() {
        boolean isRunnable = canvasPanel.isRunnable();
        setRunStatus(isRunnable);
        if (isRunnable) {
            Node startNode = canvasPanel.getStartNode();
            Node endNode = canvasPanel.getEndNode();
            Dijkstra dijkstra = new Dijkstra(directedGraphCheckbox.isSelected());
            dijkstra.execute(startNode);
            canvasPanel.run(dijkstra.getPath(endNode));
        }
        else {
            canvasPanel.stop();
        }
    }

    private void onShowInformation() {
        InformationDialog dialog = new InformationDialog();
        dialog.setVisible(true);
    }

    private void setRunStatus(boolean isReadyToRun) {
        boolean enabled = !isReadyToRun;
        drawNodeRadio.setEnabled(enabled);
        moveNodeRadio.setEnabled(enabled);
        showWeightCheckbox.setEnabled(enabled);
        directedGraphCheckbox.setEnabled(enabled);
        cleanLinesButton.setEnabled(enabled);
        cleanAllButton.setEnabled(enabled);
        exchangeButton.setEnabled(enabled);

        String text = isReadyToRun ? MessagesCode.STOP : MessagesCode.RUN;
        runOrStopButton.setText(getString(text));
    }
}
